﻿using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace IsoStorageManagerLib
{
    partial class IsoStorageManager
    {
        // NOTE: used 
        // http://james.newtonking.com/archive/2009/02/14/writing-json-to-a-file-using-json-net 
        
        private static readonly string appUid;
        private static readonly JsonSerializer jsonSerializer
            = new JsonSerializer { NullValueHandling = NullValueHandling.Ignore, MissingMemberHandling = MissingMemberHandling.Ignore, 
                DefaultValueHandling = DefaultValueHandling.Ignore, TypeNameHandling = TypeNameHandling.Auto};


        static IsoStorageManager()
        {
            var data = new byte[12];
            var csProvider = new RNGCryptoServiceProvider();
            csProvider.GetBytes(data);

            appUid = Convert.ToBase64String(data);
        }
        /*
        // Plain strings (Not thread-safe)
        // http://stackoverflow.com/questions/17935624/storagefile-50-times-slower-than-isolatedstoragefile/23213093

        public static async Task<String> ReadTextEx(String fileName)
        {
            if (String.IsNullOrEmpty(fileName))
                return String.Empty;

            return await await Task.Factory.StartNew(async () =>
            {
                using (var store = IsolatedStorageFile.GetUserStoreForApplication())
                using (var stream = new IsolatedStorageFileStream(fileName, FileMode.Open, store))
                using (var r = new StreamReader(stream))
                    return await r.ReadAsyncThread();
            });
        }
        public static async Task<bool> WriteTextEx(String fileName, String content)
        {
            if (String.IsNullOrEmpty(fileName))
                return false;

            return await await Task.Factory.StartNew(async () =>
            {
                using (var store = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    store.CreateFolderIfNecessary(fileName);

                    using (var stream = new IsolatedStorageFileStream(fileName, FileMode.Create, store))
                    using (var r = new StreamWriter(stream))
                        await r.WriteAsyncThread(content);
                }

                return true;
            });
        }
        


        // Async Jsons
        // https://nitoasyncex.codeplex.com/wikipage?title=AsyncReaderWriterLock

        private static readonly AsyncReaderWriterLock readerLocker = new AsyncReaderWriterLock(); // Should work, but throws exceptions

        public static async Task<T> ReadJsonEx<T>(String fileName)
        {
            if (String.IsNullOrEmpty(fileName))
                return default(T);

            return await await Task.Factory.StartNew(async () =>
            {
                //using (var locker = await readerLocker.ReaderLockAsync())
                using (var store = IsolatedStorageFile.GetUserStoreForApplication())
                using (var stream = new IsolatedStorageFileStream(fileName, FileMode.Open, store))
                using (var sr = new StreamReader(stream))
                using (var jr = new JsonTextReader(sr))
                    return await jr.ReadJsonAsyncTask<T>();
            });
        }
        public static async Task<bool> WriteJsonEx<T>(String fileName, T content)
        {
            if (String.IsNullOrEmpty(fileName))
                return false;

            return await await Task.Factory.StartNew(async () =>
            {
                //using (var locker = await readerLocker.WriterLockAsync())
                using (var store = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    store.CreateFolderIfNecessary(fileName);

                    using (var stream = new IsolatedStorageFileStream(fileName, FileMode.Create, store))
                    using (var sw = new StreamWriter(stream))
                    using (var jw = new JsonTextWriter(sw))
                        await jw.WriteJsonAsyncTask(content);
                }

                return true;
            });
        }
        


        // AwaitableCriticalSection
        // http://qedcode.com/content/awaitable-critical-section

        private static readonly AwaitableCriticalSection _criticalSection = new AwaitableCriticalSection();

        public static async Task<T> ReadJsonCs<T>(String fileName)
        {
            if (String.IsNullOrEmpty(fileName))
                return default(T);

            return await await Task.Factory.StartNew(async () =>
            {
                using (var section = await _criticalSection.EnterAsync())
                using (var store = IsolatedStorageFile.GetUserStoreForApplication())
                using (var stream = new IsolatedStorageFileStream(fileName, FileMode.Open, store))
                using (var sr = new StreamReader(stream))
                using (var jr = new JsonTextReader(sr))
                    return await jr.ReadJsonAsyncTask<T>();
            });
        }
        public static async Task<bool> WriteJsonCs<T>(String fileName, T content)
        {
            if (String.IsNullOrEmpty(fileName))
                return false;

            return await await Task.Factory.StartNew(async () =>
            {
                using (var section = await _criticalSection.EnterAsync())
                using (var store = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    store.CreateFolderIfNecessary(fileName);

                    using (var stream = new IsolatedStorageFileStream(fileName, FileMode.Create, FileAccess.Write, store))
                    using (var sw = new StreamWriter(stream))
                    using (var jw = new JsonTextWriter(sw))
                        await jw.WriteJsonAsyncTask(content);
                }

                return true;
            });
        }
        */


        // Mutex
        // http://stackoverflow.com/questions/23582262/async-reader-writer-locker/23583283

        public static async Task<T> ReadJsonAsync<T>(String fileName)
        {
            if (String.IsNullOrEmpty(fileName)) 
                throw new ArgumentNullException();

            return await TaskEx.Run(() =>
            {
                //var name = String.Format("{0}@{1}", AppUid, fileName);
                using (var fileMutex = new Mutex(false, String.Format("{0}@{1}", appUid, fileName)))
                {
                    try
                    {
                        fileMutex.WaitOne();
                        using (var store = IsolatedStorageFile.GetUserStoreForApplication())
                        if (store.FileExists(fileName))
                        {
                            using (var stream = new IsolatedStorageFileStream(fileName, FileMode.Open, store))
                            using (var sr = new StreamReader(stream))
                            using (var jr = new JsonTextReader(sr))
                                return jsonSerializer.Deserialize<T>(jr);
                        }
                        else
                            return default(T);
                    }
                    finally
                    {
                        fileMutex.ReleaseMutex();
                    }
                }
            });
        }
        public static async Task<bool> WriteJsonAsync<T>(String fileName, T content)
        {
            if (String.IsNullOrEmpty(fileName))
                throw new ArgumentNullException();
            
            return await TaskEx.Run(() =>
                {
                    //var name = String.Format("{0}@{1}", AppUid, fileName);
                    using (var fileMutex = new Mutex(false, String.Format("{0}@{1}", appUid, fileName)))
                    {
                        try
                        {
                            fileMutex.WaitOne();
                            using (var store = IsolatedStorageFile.GetUserStoreForApplication())
                            {
                                store.CreateFolderIfNecessary(fileName);
                                using (var stream = new IsolatedStorageFileStream(fileName, FileMode.Create, FileAccess.Write, store))
                                using (var sw = new StreamWriter(stream))
                                using (var jw = new JsonTextWriter(sw))
                                    jsonSerializer.Serialize(jw, content);
                            }
                        }
                        finally
                        {
                            fileMutex.ReleaseMutex();
                        }

                        return true;
                    }
                });
        }




        public static T ReadJson<T>(String fileName) where T : new()
        {
            if (String.IsNullOrEmpty(fileName))
                throw new ArgumentNullException();

            using (var fileMutex = new Mutex(false, String.Format("{0}@{1}", appUid, fileName)))
            try
            {
                fileMutex.WaitOne();
                using (var store = IsolatedStorageFile.GetUserStoreForApplication())
                    if (store.FileExists(fileName))
                    {
                        using (var stream = new IsolatedStorageFileStream(fileName, FileMode.OpenOrCreate, FileAccess.Read, store))
                        using (var reader = new StreamReader(stream))
                        using (var jr = new JsonTextReader(reader))
                            return jsonSerializer.Deserialize<T>(jr);
                    }
                    else
                        return default(T);
            }
            finally
            {
                fileMutex.ReleaseMutex();
            }
        }

        public static void WriteJson<T>(String fileName, T data)
        {
            if (String.IsNullOrEmpty(fileName))
                throw new ArgumentNullException();

            using (var fileMutex = new Mutex(false, String.Format("{0}@{1}", appUid, fileName)))
            try
            {
                fileMutex.WaitOne();
                using (var store = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    store.CreateFolderIfNecessary(fileName);
                    using (var stream = new IsolatedStorageFileStream(fileName, FileMode.Create, FileAccess.Write, store))
                    using (var sw = new StreamWriter(stream))
                    using (var jw = new JsonTextWriter(sw))
                        jsonSerializer.Serialize(jw, data);
                }
            }
            finally
            {
                fileMutex.ReleaseMutex();
            }
        }
        
    }
}
